﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Controls.Primitives;
using System.Windows.Navigation;

using Heaven.Graphics;
using Heaven.Texturing;
using Heaven.Texturing.Importers;
using Heaven.Geometry.Procedural;
using Heaven.Geometry;
using Heaven.Mathematics;

using Heaven.Engine;
using Heaven.Engine.Animation;
using Heaven.Engine.Transformations;

using Color = Heaven.Graphics.Color;
using Colors = Heaven.Graphics.Colors;
using CameraBrowser = Heaven.Engine.CameraBrowser;
using Wavefront = Heaven.Geometry.Importers.Wavefront;
using Visual = Heaven.Engine.Visual;

namespace Heaven.Showcase.Demos
{
    /// <summary>
    /// Demonstrations. Textures demo
    /// </summary>
	public partial class Textures
    {
        #region Fields

        // Heaven device compatible with 
        // Windows Presentation Foundation
        Heaven.SlimDX.Graphics.WindowsPresentationDevice device = null;
        // Scene, as a container for objects
        Scene scene = null;

        // Camera with particular behaviour
        CameraBrowser camera = new CameraBrowser();
        // It's for dragging
        double mouseX = Double.NaN;
        double mouseY = Double.NaN;
                
        // We have to update the property W from 0 to 1
        Property<Double> noisePosition = new Property<Double>(0);

        // Render target
        RenderTarget renderTarget = new RenderTarget(1024, 1024, LayerFormats.A8R8G8B8);
        Visual renderTargetNode = null;

        #endregion

        #region Initializations

        /// <summary>
        /// Constructor
        /// </summary>
        public Textures()
		{
			this.InitializeComponent();
            this.Loaded += new RoutedEventHandler(OnLoaded);
            this.Unloaded += new RoutedEventHandler(OnUnloaded);
            targetImage.SizeChanged += new SizeChangedEventHandler(OnSizeChanged);
            targetImage.MouseMove += new MouseEventHandler(OnMouseMove);
            targetImage.MouseWheel += new MouseWheelEventHandler(OnMouseWheel);
  
            // Initialization of graphics device
            device = new Heaven.SlimDX.Graphics.WindowsPresentationDevice(targetImage, Render);
            device.DefaultColor = new Color(0, 0, 0, 0);
            
            // Tuning the scene
            scene = new Scene(camera);
            camera.Near = 0.01;
            camera.HorisontalRotation = 0.84f;
            camera.VerticalRotation = 1.07f;
            camera.Target = Vector3.Zero;
            camera.Distance = 70;
            LoadVisuals();

            Unloaded += new RoutedEventHandler(OnUnloaded);
        }

        /// <summary>
        /// Destructor
        /// </summary>
        ~Textures()
        {
            
        }

        void OnMouseWheel(object sender, MouseWheelEventArgs e)
        {
            camera.Distance *= e.Delta > 0 ? 1.05 : 0.95;
            Invalidate();
        }

        #endregion

        #region Materials construction

        /*Technique CreateMaterial()
        {
            // Creating custom node
            
            InputValueNode input = new InputValueNode(SocketTypes.FloatFour);
            OutputValueNode output = new OutputValueNode(SocketTypes.FloatThree);
            
            CustomGPUNode gpunode = new CustomGPUNode();
            Socket inputSocket = new Socket(SocketTypes.FloatFour);
            Socket outputSocket = new Socket(SocketTypes.FloatThree);
            gpunode.Inputs.Add(inputSocket);
            gpunode.Outputs.Add(outputSocket);
            gpunode.Code = "float3 f = lod2d(.....";
             
            Graph graph = new Graph();
            graph.Nodes.Add(input);
            graph.Nodes.Add(output);
            graph.Nodes.Add(gpunode);
            graph.Connectors.Add(new Connector(input.Out, inputSocket));
            graph.Connectors.Add(new Connector(output.In, outputSocket));
            
            Technique result = new Technique(device);
            result.Pipeline.Add(Graph);
            result.Update();

            return result;
             
            
        }*/

        #endregion

        #region Manipulation routines

        void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Point point = e.GetPosition(this);

                if (!Double.IsNaN(mouseX))
                {
                    camera.HorisontalRotation += (float)((point.X - mouseX) / 100);
                    camera.VerticalRotation += (float)((point.Y - mouseY) / 100);
                    Invalidate();
                }                
                
                mouseX = point.X;
                mouseY = point.Y;
            }
            else
            {
                mouseX = Double.NaN;
                mouseY = Double.NaN;
            }
        }

        void OnSizeChanged(object sender, SizeChangedEventArgs e)
        {
            camera.Aspect = (float)(e.NewSize.Width / e.NewSize.Height);
            Invalidate();
        }
        
        void OnUnloaded(object sender, RoutedEventArgs e)
        {
            //timer.Stop();
            //device = null;
            //scene = null;
            //noisePosition = null;
            //renderTarget = null;
            //renderTargetNode = null;
        }

        void OnLoaded(object sender, RoutedEventArgs e)
        {
            //timer.Start();
        }

        #endregion

        #region Loading geometries

        void LoadVisuals()
        {
            // Create plane ZX
            UnifiedVertex[] planeZXVertices = new UnifiedVertex[]
            {
                new UnifiedVertex(new Vector3(-1, 0, -1), new Vector2(1, 0)),
                new UnifiedVertex(new Vector3(+1, 0, -1), new Vector2(1, 1)),
                new UnifiedVertex(new Vector3(+1, 0, +1), new Vector2(0, 1)),
                new UnifiedVertex(new Vector3(-1, 0, +1), new Vector2(0, 0))
            };
            Polygonal planeZXMesh = new Polygonal();
            planeZXMesh.AddVertices(planeZXVertices);
            planeZXMesh.AddTriangle(2, 1, 0);
            planeZXMesh.AddTriangle(3, 2, 0);
            Visual planeZXNode = new Visual(planeZXMesh, new Technique(GetHLSL("SimpleTexturing")));
            scene.Visuals.Add(planeZXNode);

            // Create 2d texture
            PlanarTexture texturePlanar = new PlanarTexture(
                LoadLayerFormTGA("ImageSample512"),
                new PlanarLayer[] { LoadLayerFormTGA("ImageSample256") });
            planeZXNode.Properties["InputTexture"] = new Property<Texture>(texturePlanar);

            // Create sample CubeMap
            CubicTexture cubemap = new CubicTexture(texturePlanar, texturePlanar, texturePlanar, texturePlanar, texturePlanar, texturePlanar);
            Sphere sphere = new Sphere(new Vector3(0, 0.6, 0), 0.5);
            Polygonal cubemapMesh = sphere.Generate();
            Visual cubemapNode = new Visual(cubemapMesh, new Technique(GetHLSL("CubemapTexturing")));
            cubemapNode.Properties["CubeMap"] = new Property<Texture>(cubemap);
            scene.Visuals.Add(cubemapNode);

            // Create sample VolumeTexture
            byte[] data = new byte[256 * 256 * 256 * 4];
            Random random = new Random();
            random.NextBytes(data);
            VolumeLayer volumeLayer0 = new VolumeLayer(data, 256, 256, 256, LayerFormats.A8R8G8B8);

            data = new byte[128 * 128 * 128 * 4];
            for(int i = 0; i < data.Length; i++) data[i] = 100;            
            VolumeLayer volumeLayer1 = new VolumeLayer(data, 128, 128, 128, LayerFormats.A8R8G8B8);
            
            VolumeTexture volumeTexture = new VolumeTexture(volumeLayer0, new VolumeLayer[] { volumeLayer1 });
            Visual planeNoisedNode = new Visual(planeZXMesh, new Technique(GetHLSL("VolumeTexturing")));
            planeNoisedNode.Properties["InputTexture"] = new Property<Texture>(volumeTexture);
            planeNoisedNode.Properties["W"] = noisePosition;
            planeNoisedNode.Transformation.Add(new ScaleTransformation(1.5, 1.5, 1.5));
            planeNoisedNode.Transformation.Add(new TranslationTransformation(new Vector3(0, -0.3, 0)));
            scene.Visuals.Add(planeNoisedNode);


            // Render target node
            renderTargetNode = new Visual(planeZXMesh, planeZXNode.Technique);
            renderTargetNode.Properties["InputTexture"] = new Property<Texture>(renderTarget);
            renderTargetNode.Transformation.Add(new RotationTransformation(Vector3.XAxis, Vector3.Zero, Math.PI / 2.0));
            renderTargetNode.Transformation.Add(new TranslationTransformation(new Vector3(0, 1, 1)));
            scene.Visuals.Add(renderTargetNode);

            camera.Target = Vector3.Zero;
            camera.Distance = 10;
        }


        Visual AddVisual(Polygonal polygonal)
        {
            Visual visual = new Visual(polygonal);
            visual.Technique.Properties["Color"] = new Property<Color>(new Heaven.Graphics.Color(1.0, 0, 0, 1.0));
            scene.Visuals.Add(visual);
            return visual;
        }

        PlanarLayer LoadLayerFormTGA(string resourceName)
        {
            Uri fileUri = new Uri("pack://application:,,,/Heaven.Showcase;component/Resources/Textures/" + resourceName + ".tga", UriKind.Absolute);
            using (Stream stream = Application.GetResourceStream(fileUri).Stream)
            {
                Truevision tga = new Truevision(stream);
                return tga.GetTextureLayer();
            }
        }

        string GetHLSL(string resourceName)
        {
            Uri fileUri = new Uri("pack://application:,,,/Heaven.Showcase;component/Resources/Shaders/" + resourceName + ".hlsl", UriKind.Absolute);
            using (Stream stream = Application.GetResourceStream(fileUri).Stream)
            {
                StreamReader reader = new StreamReader(stream);
                return reader.ReadToEnd();
            }
        }

        void Test_RecreateRT()
        {
            RenderTarget old = renderTarget;
            renderTarget = new RenderTarget(1024, 1024, LayerFormats.A8R8G8B8);
            renderTargetNode.Properties["InputTexture"] = new Property<Texture>(renderTarget);
            old.Dispose();
        }

        #endregion

        #region Rendering

        
        void Invalidate()
        {
            device.Invalidate();
        }

        void Render()
        {
            //Test_RecreateRT();

            device.Begin();

            // Animate noise
            noisePosition.Value += 0.001;
            if (noisePosition.Value > 1.0) noisePosition.Value = 0;
  
            // Fill render target
            device.RenderTarget = renderTarget;
            device.Clear();
            scene.Render(device);
            device.RenderTarget = null;
            byte[] data = renderTarget.Data;

            
            // Main render
            device.Clear();
            scene.Render(device);
            device.Present();
        }

        #endregion
    }
}